package com.fima.cardsui.objects; import java.lang.reflect.Field; import java.util.HashMap; import android.util.Log; /** * Contains method(s) to create a {@link Card}-family object from its * serializable model. * * <p> * Note that any similarities to Card Factory Ltd * <http://www.cardfactory.eu.com/> are purely accidental. * * @author FLamparski * */ public class CardFactory { /** * Uses Reflection to create a new {@link AbstractCard} from the given * {@link CardModel} by copying fields. * * @param model * The {@link CardModel} to "inflate" into an * {@link AbstractCard} * @return An {@link AbstractCard} that matches the model data * @throws InstantiationException * Thrown when the class specified by the model cannot be * instantiated (no default ctor). * @throws IllegalAccessException * Thrown if I missed a setAccessible(true) somewhere, or if the * default ctor for the target class is not visible. */ public static AbstractCard createCard(CardModel model) throws InstantiationException, IllegalAccessException { /* * Instantiate a new object (must be AbstractCard or extend it, makes * sense for the CardsUI mechanic) from a Class descriptor. */ AbstractCard newCard = model.cardClass.newInstance(); Log.i("CardFactory", "Creating a new card! We're making a new " + model.cardClass.getName()); /* * We will also need the Class descriptor for the object we just created * in order to access its fields. */ Class<? extends AbstractCard> newCardClazz = newCard.getClass(); /* * Java objects do not flatten their hierarchy at runtime (that's good), * which means that we actually need to collect all fields from all the * ancestors. Here, we collect them into a HashMap for convenience. */ HashMap<String, Field> destinationFields = new HashMap<String, Field>(); Class<?> clazzUnderInspection = newCardClazz; while (clazzUnderInspection != null) { /* * Get all fields for the current point in the hierarchy, and * collect them into the HashMap. */ Field[] clazzFields = clazzUnderInspection.getDeclaredFields(); for (int i = 0; i < clazzFields.length; i++) { Field f = clazzFields[i]; destinationFields.put(f.getName(), f); } /* * Okay, now examine the ancestor (for java.lang.Object it is null, * which means the loop will exit). */ clazzUnderInspection = clazzUnderInspection.getSuperclass(); } /* * Obtain a list of fields within the model. As most of them match those * in AbstractCard, the card's content will be preserved. Since the * model inherits only from Object, all fields collected here will be * what we need. */ Field[] sourceFields = model.getClass().getDeclaredFields(); /* * Now iterate over the fields in the model. */ for (int i = 0; i < sourceFields.length; i++) { // Just a reference for the field we're copying in this pass Field curField = sourceFields[i]; /* * This is to prevent IllegalAccessExceptions when accessing the * field. Yes, this violates the visibility set by the field * declarations, but that's the whole point of this routine. */ curField.setAccessible(true); String fieldName = curField.getName(); Log.d("CardFactory", " > Now copying field: " + fieldName); Field destField = destinationFields.get(fieldName); if (destField != null) { /* * We need to get the specific field that matches the one in the * model and set it to the same value as the corresponding field * in the model. */ destField.setAccessible(true); // "Trust me." destField.set(newCard, curField.get(model)); Log.d("CardFactory", String.format( " > Field %s (= %s) -> Field %s (=%s)", curField, curField.get(model), destField.getName(), destField.get(newCard))); } else { /* * We have encountered a field (CardModel.data, * CardModel.cardClass, ...) in the model that does not exist in * the view that can represent it, so we just skip it. Yes, this * breaks the "for loop is fixed" convention, but this really is * the easiest way. * * "Go to Next Iteration. Go directly to Next Iteration. Do not * pass Go. Do not collect $200." */ Log.d("CardFactory", String.format( " > Skipping over an unmapped Field %s/%s (= %s)", model.cardClass.getName(), curField, curField.get(model))); } } return newCard; } }